﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;
using FRW.Common.Extensions;

namespace Money
{
    public class CategoryRemovedEventArgs : EventArgs
    {
        private int _categoryId;

        public int CategoryId { get { return _categoryId; } }

        public CategoryRemovedEventArgs(int categoryId)
        {
            _categoryId = categoryId;
        }
    }

    public class Categories : EventList<Category>
    {
        #region Fields

        public enum DefaultCategories
        {
            Blank = 0,
            Unknown,

            // Add some default high level categories
            General,
            Bills,
            Household,
            Income,
            Variable,
            Luxury,
            Ignore,
            Loans,
            EndOfPredefined = 99
        }

        private static int _categoryNumber = Convert.ToInt32(Categories.DefaultCategories.EndOfPredefined);

        // HACK
        private static Categories _categories;

        // TODO if the list is null should we populate with the default categories?
        public static Categories CategoryList 
        { 
            get 
            {
                if (_categories == null)
                {
                    Categories newCategories = new Categories();
                    newCategories.AddDefaultCategories();
                    _categories = newCategories;
                }

                return _categories; 
            } 
        }

        #endregion

        #region Contructors

        private void AddDefaultCategories()
        {
            // check that we have the default categories available
            foreach (Categories.DefaultCategories category in Enum.GetValues(typeof(Categories.DefaultCategories)))
            {
                this.Add(new Category() { Id = (int)category, Name = category.ToString(), ParentId = 0 });
            }
        }

        public Categories()
        {
            // plumb in the event handlers that we want to use
            this.BeforeItemAdded += Category_BeforeItemAdded;
            this.BeforeItemRemoved += Categories_BeforeItemRemoved;
            this.ItemAdded += Categories_ItemAdded;
            this.ItemRemoved += Categories_ItemRemoved;

            // HACK
            _categories = this;
        }

        #endregion

        public IEnumerable<string> GetCategoryNames()
        {
            foreach (Category category in this)
            {
                yield return category.Name;
            }
        }

        public IEnumerable<Category> GetChildren(int parentId)
        {
            var children = from category in this where category.ParentId == parentId select category;

            return children.ToList();
        }

        public string GetCategoryName(int categoryId)
        {
            string name = "Unknown";

            Category category = this.Find(cat => cat.Id == categoryId);
            
            if (category != null)
            {
                return category.Name;
            }

            return name;
        }

        public int GetCategoryId(string categoryName)
        {
            int categoryId = 0;

            Category category = this.Find(cat => cat.Name.Equals(categoryName));

            if (category != null)
            {
                categoryId = category.Id;
            }

            return categoryId;
        }

        public int GetCategoryId(DefaultCategories defaultCategory)
        {
            int categoryId = Convert.ToInt32(defaultCategory);

            return categoryId;
        }

        public int GetParentId(string categoryName)
        {
            int parentId = 0;

            parentId = GetParentId(GetCategoryId(categoryName));

            return parentId;
        }

        public int GetParentId(int categoryId)
        {
            int parentCategoryId = 0;

            Category category = this.Find(cat => cat.Id == categoryId);

            if (category != null)
            {
                if (category.ParentId == 0)
                {
                    parentCategoryId = category.Id;
                }
                else
                {
                    if (category.ParentId == category.Id)
                    {
                        throw new InvalidProgramException("Parent ID is the same as the item ID, recursive issue.");
                    }

                    parentCategoryId = GetParentId(category.ParentId);
                }
            }

            return parentCategoryId;
        }

        public List<int> GetCategoryChain(int categoryId, List<int> chain)
        {
            Category category = this.Find(cat => cat.Id == categoryId);

            if (category != null)
            {
                if (category.ParentId != 0)
                {
                    chain.Add(category.ParentId);
                    chain = GetCategoryChain(category.ParentId, chain);
                }
            }

            return chain;
        }

        public IEnumerable<Category> GetCategoryGroups()
        {
            foreach (Category category in _categories)
            {
                if (category.ParentId == Convert.ToInt32(Categories.DefaultCategories.Blank))
                {
                    yield return category;
                }
            }
        }

        public string GetFullCategoryName(int categoryId)
        {
            string category = "";
            List<int> chain = new List<int>();
            chain.Add(categoryId);

            chain = GetCategoryChain(categoryId, chain);

            IEnumerator<int> enumerator = chain.GetEnumerator();

            enumerator.Reset();
            enumerator.MoveNext();

            category += GetCategoryName(enumerator.Current);

            while (enumerator.MoveNext())
            {
                category += " > " + GetCategoryName(enumerator.Current);
            }

            return category;
         }

        public string GetParentName(int categoryId)
        {
            return GetCategoryName(GetParentId(categoryId));
        }

        public static Expression<Func<Category, bool>> ParentId(int parentId)
        {
                return c => c.ParentId == parentId;
        }

        public bool Exists(string name)
        {
            bool exists = false;

            if (Find(category => category.Name.Equals(name)) != null)
            {
                // HACK we need to tidy up the lists first before we prevent duplicates
                exists = true;
            }

            return exists;
        }

        private void OnUpdated()
        {
            if (Updated != null)
            {
                Updated(this, new EventArgs());
            }
        }

        #region Event Handlers

        void Category_BeforeItemAdded(object sender, ListPreEventArgs<Category> e)
        {
            Category category = e.Item;

            // TODO are there any checks we want to carry out before allowing the transaction to be added
            if ( ( category.Id == Convert.ToInt32(Categories.DefaultCategories.EndOfPredefined) )
              || ( Exists(category.Name) ) )
            {
                e.Cancel = true;
            }
            else
            {
                // check if this item has a category number, if not we need to add one
                // TODO we may need to tidy-up the handling of blank category as a special case
                if (category.Id == 0 && !category.Name.Equals("Blank"))
                {
                    _categoryNumber++;
                    category.Id = _categoryNumber;
                }
                else
                {
                    if (category.Id > _categoryNumber)
                    {
                        _categoryNumber = category.Id;
                    }
                }
            }
        }

        private void Categories_BeforeItemRemoved(object sender, ListPreEventArgs<Category> e)
        {
            // TODO we should tidy this up so we have a minimum value for user defined types and check for that.
            if (e.Item.Id <= Convert.ToInt32(Categories.DefaultCategories.EndOfPredefined))
            {
                e.Cancel = true;
            }
            else
            {
                // TODO this should probably be done after the item has been removed but the event handler does not give access to the item removed
                if (CategoryRemoved != null)
                {
                    CategoryRemovedEventArgs eventArgs = new CategoryRemovedEventArgs(e.Item.Id);

                    CategoryRemoved(this, eventArgs);
                }
            }
        }

        void Categories_ItemRemoved(object sender, EventArgs e)
        {
            OnUpdated();
        }

        void Categories_ItemAdded(object sender, ListEventArgs<Category> e)
        {
            OnUpdated();
        }

        #endregion

        public event EventHandler<CategoryRemovedEventArgs> CategoryRemoved;
        public event EventHandler<EventArgs> Updated;
    }
}
